use std::{collections::HashMap, default, error::Error, sync::Arc};

use crate::{
    security::LoginUser,
    error::{AppError, AppResult},
    extract::Json,
    utils::get_context_key,
    AppState,
};
use axum::{
    extract::{rejection::NestedPathRejection, MatchedPath, NestedPath, Path, Query, State},
    http::Uri,
    response::{Html, IntoResponse, Response},
    routing::{delete, get, post, put},
    Extension, Router,
};
use common::page::{self, PageParams};
use gen_table::{JsonMap, JsonValue, SqlModel};
use loginmanager::AuthUser;
use sea_orm::Value;
use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize)]
struct ExtSqlModel {
    name: String,
    #[serde(flatten)]
    model: SqlModel,
}

#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Hash)]
enum Method {
    #[default]
    GET,
    POST,
    PUT,
    DELETE,
}

impl From<axum::http::Method> for Method {
    fn from(value: axum::http::Method) -> Self {
        if value == axum::http::Method::GET {
            return Self::GET;
        }
        if value == axum::http::Method::POST {
            return Self::POST;
        }
        if value == axum::http::Method::PUT {
            return Self::PUT;
        }
        if value == axum::http::Method::DELETE {
            return Self::DELETE;
        }
        panic!("unexpect {value}");
    }
}

#[derive(Debug, Deserialize, Default)]
struct SqlMethodCfg {
    models: Vec<String>,
    /// 是否输出 jinja 模板
    template: Option<String>,
    /// 筛选sql查询的结果
    data: Option<Vec<String>>,
}

#[derive(Debug, Deserialize, Default)]
struct SqlRouterCfg {
    models: HashMap<String, SqlModel>,
    #[serde(flatten)]
    methods: HashMap<Method, SqlMethodCfg>,
}

impl SqlRouterCfg {
    pub fn get_model(&self, key: &str) -> Option<&SqlModel> {
        self.models.get(key)
    }
}

type SqlRouters = HashMap<String, SqlRouterCfg>;

fn load_routers() -> SqlRouters {
    let mut routers: SqlRouters = HashMap::new();
    if let Ok(read_dir) = std::fs::read_dir("config/sqlview") {
        read_dir
            .filter_map(Result::ok)
            .filter(|f| !f.path().ends_with(".yml"))
            .map(|f| std::fs::read_to_string(f.path()))
            .filter_map(Result::ok)
            .map(|f| serde_yaml::from_str::<SqlRouters>(&f).unwrap())
            .for_each(|f| routers.extend(f));
    }
    routers
}

pub fn get_route<S>() -> Router<S>
where
    S: Sync + Send + Clone + 'static,
{
    println!("Start loading sql view configuration");
    let mut router = Router::<S>::new();
    let cfgs = load_routers();
    let mut tera = tera::Tera::default();
    for (key, cfg) in &cfgs {
        for (method, mcfg) in &cfg.methods {
            let method_router = match method {
                Method::GET => get(render),
                Method::POST => post(render),
                Method::PUT => put(render),
                Method::DELETE => delete(render),
            };
            router = router.route(&key, method_router);
            for i in &mcfg.models {
                if !cfg.models.contains_key(i) {
                    panic!("panic: route {key} method {method:?} miss model {i}");
                }
            }
        }
        for (name, sql) in &cfg.models {
            if let Err(e) = tera.add_raw_template(&format!("{key}_{name}"), sql.sql()) {
                panic!(
                    "panic error sql template: {} \n{:?}",
                    e.to_string(),
                    e.source()
                );
            }
        }
    }
    println!("Complete loading sql view configuration\n");
    router
        .route_layer(Extension(Arc::new(cfgs)))
        .route_layer(Extension(Arc::new(tera)))
}

async fn render(
    uri: Uri,
    metched_path: MatchedPath,
    nested_path: Option<NestedPath>,
    method: axum::http::Method,
    AuthUser(user): AuthUser<Option<LoginUser>>,
    Path(paths): Path<HashMap<String, String>>,
    Query(query): Query<HashMap<String, String>>,
    Query(page_params): Query<PageParams>,
    Extension(state): Extension<AppState>,
    Extension(cfgs): Extension<Arc<SqlRouters>>,
    Extension(tera): Extension<Arc<tera::Tera>>,
    Json(data): Json<serde_json::Value>,
) -> AppResult<Response> {
    let key = match nested_path {
        Some(s) => metched_path.as_str().trim_start_matches(s.as_str()),
        None => metched_path.as_str(),
    };
    let route_cfg = cfgs.get(key).ok_or(AppError::NotFount)?;

    let method: Method = method.into();
    let method_cfg = route_cfg.methods.get(&method).ok_or(AppError::NotFount)?;

    let mut result: JsonMap = JsonMap::new();
    let user = serde_json::to_value(user).unwrap_or(JsonValue::Null);
    let mut context = state.tera_context();
    context.insert("data", &data);
    context.insert("user", &user);
    context.insert("path", &paths);
    context.insert("query", &query);
    context.insert("method", &method);
    context.insert("page", &into_page_map(&page_params));

    for (i, model_name) in method_cfg.models.iter().enumerate() {
        let sql = tera.render(&format!("{key}_{model_name}"), &context)?;
        tracing::debug!("sql template: {sql}");
        let model = route_cfg.get_model(&model_name).ok_or(AppError::NotFount)?;
        let value = model
            .exec_sql(state.db(), &sql, |k| {
                if let Some(v) = get_context_key(k, &context) {
                    return Value::from(v);
                }
                Value::from(k)
            })
            .await?;
        context.insert(model_name, &value);
        result.insert(model_name.clone(), value);
    }

    let mut result2: JsonMap = JsonMap::new();
    if let Some(keys) = method_cfg.data.as_ref() {
        for key in keys {
            if let Some(value) = result.remove(key) {
                result2.insert(key.to_owned(), value);
            };
        }
    } else {
        result2 = result;
    }

    if let Some(template) = &method_cfg.template {
        return state
            .render_template(template.trim_start_matches("/"), &context)
            .map(IntoResponse::into_response)
            .map_err(Into::into);
    }

    if result2.len() == 1 {
        for (_, v) in result2 {
            return Ok(Json(v).into_response());
        }
        return Err(AppError::NotFount);
    } else {
        return Ok(Json(JsonValue::Object(result2)).into_response());
    }
}

fn into_page_map(page_params: &PageParams) -> JsonMap {
    let mut page: JsonMap = JsonMap::new();
    page.insert(
        "size".to_owned(),
        serde_json::Number::from(page_params.page_size()).into(),
    );
    page.insert(
        "page".to_owned(),
        serde_json::Number::from(page_params.page()).into(),
    );
    page.insert(
        "zero_page".to_owned(),
        serde_json::Number::from(page_params.zero_page()).into(),
    );
    page.insert(
        "limit".to_owned(),
        serde_json::Number::from(page_params.limit()).into(),
    );
    page.insert(
        "offset".to_owned(),
        serde_json::Number::from(page_params.offset()).into(),
    );
    page
}
